﻿using System;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq.Expressions;
using EntityFramework.Extensions;

//http://www.cnblogs.com/haogj/archive/2011/05/06/2038965.html  EF 学习教程
namespace TSharp.Core.Repository.EntityFramework
{
    public class EntityFrameworkUnitOfWork : Disposable, IUnitOfWork, IBatchUnitOfWork
    {
        private readonly bool _isReadonly = false;
        private readonly bool _containDeleted;
        public EntityFrameworkUnitOfWork(DbContext dbContext, bool isReadonly, bool containDeleted)
        {
            this.DbContext = dbContext;
            this._isReadonly = isReadonly;
            _containDeleted = containDeleted;
        }

        internal DbContext DbContext { get; set; }

        public int Commit()
        {
            if (this._isReadonly)
                throw new GenericRepositoryException("工作单元为只读属性，无法提交数据！");
            var updateds = this.DbContext.ChangeTracker.Entries().Where(x => x.Entity is IRowVersioned && x.State == System.Data.EntityState.Modified);
            foreach (var update in updateds)
            {
                var v = (update.Entity as IRowVersioned);
                if (v != null)
                    v.RowVersion = v.RowVersion + 1;
            }
            try
            {
                return this.DbContext.SaveChanges();
            }
            catch (DbEntityValidationException ex)
            {
                var error = string.Join(Environment.NewLine, ex.EntityValidationErrors.SelectMany(x =>
                         x.ValidationErrors.Select(v => string.Format("Model:'{0}' Property-Error：{1}-'{2}'", x.Entry.Entity.GetType().Name, v.PropertyName, v.ErrorMessage))));

                throw new Core.Exceptions.DbEntityValidationException(error, ex);
            }
            catch (DbUpdateConcurrencyException concurrency)
            {
                throw new Core.Exceptions.DbUpdateConcurrencyException("数据库未更新，可能数据已经发生修改。", concurrency);
            }
            catch (DbUpdateException e)
            {
                throw new Core.Exceptions.DbUpdateException(e.Message, e);
            }

        }

        public ITransaction BeginTransaction()
        {
            return new EntityFrameworkTransaction(this);
        }

        public void EndTransaction(ITransaction transaction)
        {
            if (transaction != null)
            {
                using (transaction)
                {

                }
            }
        }
        #region Batch Operation

        public int Delete<TEntity>(IQueryable<TEntity> deleteWhere) where TEntity : class
        {
            return deleteWhere.Delete();
        }
        public int Delete<TEntity>(Expression<Func<TEntity, bool>> filterExpression) where TEntity : class
        {
            return this.DbContext.Set<TEntity>().Where(filterExpression).Delete();
        }
        public int Update<TEntity>(Expression<Func<TEntity, bool>> filterExpression,
           Expression<Func<TEntity, TEntity>> updateExpression)
           where TEntity : class
        {
            return this.DbContext.Set<TEntity>().Where(filterExpression).Update(updateExpression);
        }
        public int Update<TEntity>(IQueryable<TEntity> updateWhere,
            Expression<Func<TEntity, TEntity>> updateExpression)
        where TEntity : class
        {
            return updateWhere.Update(updateExpression);
        }

        #endregion
        public TEntity Insert<TEntity>(TEntity entity) where TEntity : class
        {
            return this.DbContext.Set<TEntity>().Add(entity);
        }

        public TEntity Update<TEntity>(TEntity entity) where TEntity : class
        {
            //var stateEntity = this.DbContext.Entry<TEntity>(entity);
            //if (stateEntity.State == System.Data.EntityState.Detached)
            //{
            //    this.DbContext.Set<TEntity>().Attach(entity);
            //}

            //if (this.DbContext.Entry<TEntity>(entity).State == System.Data.EntityState.Unchanged)
            //{
            //    this.DbContext.Entry<TEntity>(entity).State = System.Data.EntityState.Modified;
            //}
            return entity;
        }

        public TEntity BeginUpdate<TEntity>(TEntity entity) where TEntity : class
        {
            var stateEntity = this.DbContext.Entry<TEntity>(entity);
            if (stateEntity.State == System.Data.EntityState.Detached)
            {
                this.DbContext.Set<TEntity>().Attach(entity);
            }

            //if (this.DbContext.Entry<TEntity>(entity).State == System.Data.EntityState.Unchanged)
            //{
            //    this.DbContext.Entry<TEntity>(entity).State = System.Data.EntityState.Modified;
            //}
            return stateEntity.Entity;
        }
        public TEntity Update<TEntity>(TEntity entity, Action<TEntity> doUpdate) where TEntity : class
        {
            if (doUpdate == null)
                throw new ArgumentNullException("doUpdate");
            var stateEntity = this.DbContext.Entry<TEntity>(entity);
            if (stateEntity.State == System.Data.EntityState.Detached)
            {
                this.DbContext.Set<TEntity>().Attach(entity);
            }
            //if (this.DbContext.Entry<TEntity>(entity).State == System.Data.EntityState.Unchanged)
            //{
            //    this.DbContext.Entry<TEntity>(entity).State = System.Data.EntityState.Modified;
            //}

            doUpdate(entity);
            return entity;
        }
        public TEntity Delete<TEntity>(TEntity entity) where TEntity : class
        {
            var del = entity as ISoftDelete;
            if (del != null)
            {
                del.IsDeleted = true;
                return Update(entity);
            }
            return RealDelete(entity);
        }
        public TEntity RealDelete<TEntity>(TEntity entity) where TEntity : class
        {
            return this.DbContext.Set<TEntity>().Remove(entity);
        }
        public TEntity GetById<TEntity>(params object[] ids) where TEntity : class
        {
            return this.DbContext.Set<TEntity>().Find(ids);
        }
        private static readonly Type SoftDeletedBaseType = typeof(ISoftDelete);
        internal IQueryable<TEntity> ToQueryable<TEntity>() where TEntity : class
        {
            IQueryable<TEntity> query = DbContext.Set<TEntity>();
            if (_isReadonly)
            {
                query = query.AsNoTracking();
            }
            if (!_containDeleted && SoftDeletedBaseType.IsAssignableFrom(typeof(TEntity)))
            {
                query = query.Where("IsDeleted== @0", false);
            }
            return query;
        }

        private bool disposed;
        protected override void Dispose(bool disposing)
        {
            if (!disposed)
            {
                try
                {
                    if (disposing)
                    {
                        // 释放托管资源
                        if (this.DbContext != null)
                        {
                            this.DbContext.Dispose();
                            this.DbContext = null;
                        }
                    }
                    // 这里释放所有非托管资源
                    disposed = true;
                }
                finally
                {
                    base.Dispose(disposing);
                }
            }
        }


        public IEnumerable<TElement> SqlQuery<TElement>(string sql, params object[] parameters)
        {
            return DbContext.Database.SqlQuery<TElement>(sql, parameters);
        }


        public int ExecuteSqlCommand(string sql, params object[] parameters)
        {
            return DbContext.Database.ExecuteSqlCommand(sql, parameters);
        }

        public System.Collections.IEnumerable SqlQuery(Type elementType, string sql, params object[] parameters)
        {
            return DbContext.Database.SqlQuery(elementType, sql, parameters);
        }


        public IQueryable<TEntity> Find<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate) where TEntity : class
        {
            IQueryable<TEntity> query = DbContext.Set<TEntity>();
            if (_isReadonly)
            {
                query = query.AsNoTracking();
            }
            query = query.Where(predicate);
            if (!_containDeleted && SoftDeletedBaseType.IsAssignableFrom(typeof(TEntity)))
            {
                query = query.Where("IsDeleted== @0", false);
            }
            return query;

        }
    }
}
